﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Data;
using ScrumTable.Common.Excel.Properties;
using ScrumTable.Common.Logging;

#endregion

namespace ScrumTable.Common.Excel.OpenXmlSdk.Extensions
{
    /// <summary>
    /// Provides the additional functionalities required for Excel
    /// data management of a DataRow instance.
    /// </summary>
    public static class DataRowUtil
    {
        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Returns true if the current data row accommodates at least one
        /// field (expect the primary key) which is not empty.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        public static bool AccommodatesValues(this DataRow thisPtr)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            IDictionary<int, DataColumn> primaryKeys = thisPtr.Table.PrimaryKey.Convert(col => new KeyValuePair<int, DataColumn>(col.Ordinal, col));
            
            var columnsToCheck = from dataColumn in thisPtr.Table.GetColumns()
                                 where !primaryKeys.ContainsKey(dataColumn.Ordinal)
                                 select dataColumn;

            return columnsToCheck.Any(row => !((DataValue) thisPtr[row]).IsEmpty);
        }

        /// <summary>
        /// Determines whether the data table does already contain a data row
        /// with the same keys.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        public static bool IsNotContainedByPrimaryKey(this DataRow thisPtr)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            if (thisPtr.Table.HasPrimaryKey())
            {
                return (thisPtr.TryGetFirstPrimaryKey(DataValue.Empty) != DataValue.Empty)
                    && (!thisPtr.Table.Rows.Contains(thisPtr.GetPrimaryKeys()));
            }
            return true;
        }

        /// <summary>
        /// Sets the first primary key value of the current row.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        /// <param name="value">Specifies the value to write into the first PK. If the column contains a DataValue, its inner value will be overwritten.</param>
        public static void SetFirstPrimaryKey(this DataRow thisPtr, object value)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            object[] pkValues = new object[thisPtr.Table.PrimaryKey.Length];

            if (pkValues.Length > 0)
            {
                if (thisPtr[thisPtr.Table.PrimaryKey[0]] is DataValue)
                {
                    thisPtr[thisPtr.Table.PrimaryKey[0]] = ((DataValue)thisPtr[thisPtr.Table.PrimaryKey[0]]).ChangeValue(value);
                }
                else
                {
                    thisPtr[thisPtr.Table.PrimaryKey[0]] = value;
                }
            }
        }

        /// <summary>
        /// Gets the primary key values of the current row.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        public static IEnumerable<DataValue> GetPrimaryKeyValues(this DataRow thisPtr)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            DataValue[] pkValues = new DataValue[thisPtr.Table.PrimaryKey.Length];

            if (pkValues.Length > 0)
            {
                int counter = 0;
                foreach (DataColumn dataValueCol in thisPtr.Table.PrimaryKey)
                {
                    pkValues[counter++] = (DataValue)thisPtr[dataValueCol];
                }
            }
            return pkValues;
        }

        /// <summary>
        /// Sets the provided value into the column with the given column header name.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        /// <param name="columnHeaderName">Specifies the name of the column to write.</param>
        /// <param name="valueToSet">Specifies the new value of the column. If the column contains a DataValue, its inner value will be overwritten.</param>
        /// <param name="link">Specifies the link of the surrounding value to set.</param>
        public static void SetColumnValue(this DataRow thisPtr, string columnHeaderName, object valueToSet, Uri link = null)
        {
            if (thisPtr.Table.Columns.Contains(columnHeaderName))
            {
                if (thisPtr[columnHeaderName] is DataValue)
                {
                    thisPtr[columnHeaderName] = ((DataValue)thisPtr[columnHeaderName]).ChangeValue(valueToSet, link);
                }
                else
                {
                    thisPtr[columnHeaderName] = valueToSet;
                }
            }
        }

        /// <summary>
        /// Returns the column with the given column header name or a null reference if the column couldn't be retrieved.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        /// <param name="columnHeaderName">Specifies the name of the column to retrieve.</param>
        public static DataValue GetColumnValue(this DataRow thisPtr, string columnHeaderName)
        {
            if (thisPtr.Table.Columns.Contains(columnHeaderName))
            {
                return (DataValue)thisPtr[columnHeaderName];
            }
            return null;
        }

        /// <summary>
        /// Gets the untyped id of the current row.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        public static DataValue GetFirstPrimaryKey(this DataRow thisPtr)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            return thisPtr.GetPrimaryKeyValues().FirstOrDefault();
        }

        /// <summary>
        /// Gets the typed id of the current row.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        public static T GetFirstPrimaryKey<T>(this DataRow thisPtr)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            if (!thisPtr.Table.HasPrimaryKey())
            {
                throw new InvalidOperationException(Resources.ExcDtRwKeyDoesNotExist);
            }

            DataValue firstPk = thisPtr.GetFirstPrimaryKey();
                
            if (firstPk == null || firstPk.Value == null)
            {
                throw new InvalidOperationException(Resources.ExcDtRwKeyValueDoesNotExist);
            }
            return firstPk.Value.ChangeType<T>();
        }

        /// <summary>
        /// Gets the typed id of the current row.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        /// <param name="defaultValue">Specifies the default value for the id if it couldn't be found.</param>
        public static T TryGetFirstPrimaryKey<T>(this DataRow thisPtr, T defaultValue)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            DataValue idValue = thisPtr.GetFirstPrimaryKey();

            if (idValue != null)
            {
                if (typeof(T) == typeof(DataValue))
                {
                    return (T)(object)idValue;
                }

                T outValue;

                if (idValue.Value != null && idValue.Value.TryChangeType(out outValue))
                {
                    return outValue;
                }
            }
            return defaultValue;
        }

        /// <summary>
        /// Gets a typed enumerable of values.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        public static IEnumerable<DataValue> GetColumns(this DataRow thisPtr)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            return thisPtr.GetColumns<DataValue>();
        }

        /// <summary>
        /// Returns the row index of the current row. This value can only be
        /// evaluated if the row contains at least one cell.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        public static int GetReferencedRowIndex(this DataRow thisPtr)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");

            if (thisPtr.ItemArray.Length > 0)
            {
                DataValue data = (thisPtr.ItemArray[0] as DataValue);

                if (data != null && data.Reference != null)
                {
                    return data.Reference.RowIndex;
                }
            }
            return -1;
        }

        /// <summary>
        /// Returns the column with the given column header name or a null reference if the column couldn't be retrieved.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        /// <param name="defaultValue">Specifies the default value for the column.</param>
        /// <param name="columnHeaderName">Specifies the name of the column to retrieve.</param>
        public static T GetColumnValue<T>(this DataRow thisPtr, string columnHeaderName, T defaultValue = default(T))
        {
            object columnValue = GetColumnValue(thisPtr, typeof (T), columnHeaderName);
            
            if (columnValue != null)
            {
                return (T)columnValue;
            }
            return defaultValue;
        }

        /// <summary>
        /// Returns the column with the given column header name or a null reference if the column couldn't be retrieved.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        /// <param name="columnValueType">Specifies the expected result type.</param>
        /// <param name="resultColumnValue">Specifies a reference to the column result value field.</param>
        /// <param name="columnHeaderName">Specifies the name of the column to retrieve.</param>
        public static bool TryGetColumnValue(this DataRow thisPtr, Type columnValueType, string columnHeaderName, out object resultColumnValue)
        {
            resultColumnValue = null;

            try
            {
                if (thisPtr.Table.Columns.Contains(columnHeaderName))
                {
                    object valueRaw = thisPtr.GetColumnValue(columnHeaderName).Value;
                    object convertedValue;

                    if (valueRaw != null && valueRaw.TryChangeType(columnValueType, out convertedValue))
                    {
                        resultColumnValue = convertedValue;
                        return true;
                    }
                }
                return false;
            }
            catch
            {
                return false; // expected error if conversion fails
            }
        }

        /// <summary>
        /// Returns the column with the given column header name or a null reference if the column couldn't be retrieved.
        /// </summary>
        /// <param name="thisPtr">Specifies the pointer to the DataRow instance.</param>
        /// <param name="columnValueType">Specifies the expected result type.</param>
        /// <param name="columnHeaderName">Specifies the name of the column to retrieve.</param>
        public static object GetColumnValue(this DataRow thisPtr, Type columnValueType, string columnHeaderName)
        {
            PreCondition.AssertNotNull(thisPtr, "thisPtr");
            PreCondition.AssertNotNull(columnValueType, "columnValueType");

            object columnValue = null;

            if (thisPtr.Table.Columns.Contains(columnHeaderName))
            {
                object valueRaw = thisPtr.GetColumnValue(columnHeaderName).Value;
                object convertedValue;

                if (valueRaw != null && valueRaw.TryChangeType(columnValueType, out convertedValue))
                {
                    columnValue = convertedValue;
                }
            }
            return columnValue;
        }

        #endregion
    }
}